import requests
import threading
import queue
import time
import json
from typing import Optional, List
from dataclasses import dataclass, asdict
from datetime import datetime
import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(threadName)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)


@dataclass
class VerifiedPhone:
    phone_number: str
    order_id: str
    verified_at: str
    used: bool = False
    bound_email: Optional[str] = None


@dataclass
class VerifiedAccount:
    phone_number: str
    email: str
    session_token: str
    v0_code: Optional[str] = None
    elevenlabs_code: Optional[str] = None
    created_at: str = None
    
    def __post_init__(self):
        if not self.created_at:
            self.created_at = datetime.now().isoformat()


class AIStudentPackClient:
    
    def __init__(self):
        self.base_url = "https://aistudentpack.com"
        self.session = requests.Session()
        self.session_token = None
        self._setup_headers()
    
    def _setup_headers(self):
        self.session.headers.update({
            'Accept': 'application/json',
            'Accept-Language': 'zh-CN,zh;q=0.9',
            'Origin': self.base_url,
            'Referer': f'{self.base_url}/sign-in',
            'sec-ch-ua': '"Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138"',
            'sec-ch-ua-mobile': '?0',
            'sec-ch-ua-platform': '"Linux"',
            'sec-fetch-site': 'same-origin',
            'sec-fetch-mode': 'cors',
            'sec-fetch-dest': 'empty',
            'priority': 'u=1, i'
        })
    
    def _update_session_token(self, response):
        if 'session_token' in response.cookies:
            self.session_token = response.cookies['session_token']
            logger.info(f"Session token updated: {self.session_token[:30]}...")
    
    def send_phone_otp(self, phone_number: str) -> bool:
        url = f"{self.base_url}/otp"
        data = {"phoneNumber": phone_number}
        
        try:
            response = self.session.post(url, json=data)
            self._update_session_token(response)
            response.raise_for_status()
            
            result = response.json()
            if "success" in str(result).lower() or "sent" in str(result).lower():
                logger.info(f"OTP sent to {phone_number}")
                return True
            return False
        except Exception as e:
            logger.error(f"Error sending phone OTP: {e}")
            return False
    
    def verify_phone_otp(self, phone_number: str, otp: str) -> bool:
        url = f"{self.base_url}/verify-sms"
        data = {
            "phoneNumber": phone_number,
            "phoneOtp": otp
        }
        
        try:
            response = self.session.post(url, json=data)
            self._update_session_token(response)
            response.raise_for_status()
            
            result = response.json()
            if result.get("phoneVerified") == True:
                logger.info(f"Phone verified: {phone_number}")
                return True
            return False
        except Exception as e:
            logger.error(f"Error verifying phone: {e}")
            return False
    
    def send_email_otp(self, email: str) -> bool:
        url = f"{self.base_url}/email-otp"
        data = {"email": email}
        
        try:
            response = self.session.post(url, json=data)
            self._update_session_token(response)
            response.raise_for_status()
            
            result = response.json()
            if "success" in str(result).lower() or "sent" in str(result).lower():
                logger.info(f"Email OTP sent to {email}")
                return True
            return False
        except Exception as e:
            logger.error(f"Error sending email OTP: {e}")
            return False
    
    def verify_email_otp(self, email: str, otp: str, phone_number: str) -> bool:
        url = f"{self.base_url}/verify-email"
        data = {
            "email": email,
            "emailOtp": otp,
            "phoneNumber": phone_number
        }
        
        try:
            response = self.session.post(url, json=data)
            self._update_session_token(response)
            response.raise_for_status()
            
            result = response.json()
            if result.get("emailVerified") == True:
                logger.info(f"Email verified and bound: {email} -> {phone_number}")
                return True
            return False
        except Exception as e:
            logger.error(f"Error verifying email: {e}")
            return False
    
    def redeem_code(self, company_id: str) -> Optional[str]:
        url = f"{self.base_url}/redeem"
        data = {"companyId": company_id}
        
        headers = {'Content-Type': 'text/plain;charset=UTF-8'}
        
        try:
            response = self.session.post(url, json=data, headers=headers)
            response.raise_for_status()
            
            result = response.json()
            code = result.get("code", "")
            
            if company_id == "elevenlabs" and "coupon=" in code:
                code = code.split("coupon=")[1].split("&")[0]
            
            logger.info(f"Redeemed {company_id} code: {code}")
            return code
        except Exception as e:
            logger.error(f"Error redeeming {company_id} code: {e}")
            return None


class PhoneVerificationWorker(threading.Thread):
    
    def __init__(self, phone_queue: queue.Queue, phone_verifier, stop_event: threading.Event, target_accounts: int = 5, phone_config: dict = None):
        super().__init__(name="PhoneWorker")
        self.phone_queue = phone_queue
        self.phone_verifier = phone_verifier
        self.stop_event = stop_event
        self.target_accounts = target_accounts
        self.phones_purchased = 0
        self.phone_config = phone_config or {}
        self.daemon = True
    
    def run(self):
        while not self.stop_event.is_set():
            try:

                if self.phones_purchased >= self.target_accounts:
                    logger.info(f"Target reached: {self.phones_purchased} phones purchased for {self.target_accounts} accounts")
                    time.sleep(10)
                    continue
                
                if self.phone_queue.qsize() >= 2:
                    logger.info(f"Phone queue has {self.phone_queue.qsize()} phones, waiting...")
                    time.sleep(10)
                    continue
                
                logger.info(f"Purchasing phone number {self.phones_purchased + 1}/{self.target_accounts}...")
                order = self.phone_verifier.buy_number(
                    country=self.phone_config.get('phone_country', 'england'),
                    operator=self.phone_config.get('phone_operator', 'virtual60'), 
                    product=self.phone_config.get('phone_product', 'other')
                )
                
                if not order:
                    logger.error("Failed to purchase phone")
                    time.sleep(30)
                    continue
                
                logger.info(f"Phone purchased: {order.phone}")
                
                client = AIStudentPackClient()
                if not client.send_phone_otp(order.phone):
                    logger.error(f"Failed to send OTP to {order.phone}")
                    logger.info(f"Canceling order {order.id}")
                    self.phone_verifier.cancel_order(order.id)
                    continue
                
                logger.info(f"Waiting for SMS for {order.phone}...")
                sms = self.phone_verifier.wait_for_sms(
                    order.id,
                    max_wait=120,
                    check_interval=5
                )
                
                if not sms or not sms.code:
                    logger.error(f"No SMS received for {order.phone}")
                    logger.info(f"Canceling order {order.id} due to no SMS")
                    self.phone_verifier.cancel_order(order.id)
                    continue
                
                logger.info(f"SMS received with code: {sms.code}")
                
                if client.verify_phone_otp(order.phone, sms.code):
                    verified = VerifiedPhone(
                        phone_number=order.phone,
                        order_id=order.id,
                        verified_at=datetime.now().isoformat()
                    )
                    self.phone_queue.put(verified)
                    logger.info(f"✓ Phone verified and queued: {order.phone}")
                    self.phones_purchased += 1
                    logger.info(f"Successfully verified {self.phones_purchased}/{self.target_accounts} phones")
                else:
                    logger.error(f"Failed to verify {order.phone}")
                    logger.info(f"Canceling order {order.id} due to verification failure")
                    self.phone_verifier.cancel_order(order.id)
                
            except Exception as e:
                logger.error(f"Phone worker error: {e}")
                time.sleep(10)


class EmailVerificationWorker(threading.Thread):
    
    def __init__(self, phone_queue: queue.Queue, email_verifier, results_queue: queue.Queue, stop_event: threading.Event):
        super().__init__(name="EmailWorker")
        self.phone_queue = phone_queue
        self.email_verifier = email_verifier
        self.results_queue = results_queue
        self.stop_event = stop_event
        self.daemon = True
    
    def run(self):
        while not self.stop_event.is_set():
            try:
                email = self.email_verifier.generate_random_email()
                logger.info(f"Processing email: {email}")
                
                try:
                    verified_phone = self.phone_queue.get(timeout=60)
                except queue.Empty:
                    logger.warning("No verified phones available")
                    continue
                
                if verified_phone.used:
                    logger.warning(f"Phone already used: {verified_phone.phone_number}")
                    continue
                
                client = AIStudentPackClient()
                if not client.send_email_otp(email):
                    logger.error(f"Failed to send email OTP to {email}")
                    self.phone_queue.put(verified_phone)
                    continue
                
                logger.info(f"Waiting for email code for {email}...")
                email_code = self.email_verifier.get_latest_verification_code(
                    email,
                    max_wait=600
                )
                
                if not email_code:
                    logger.error(f"No email code received for {email}")
                    self.phone_queue.put(verified_phone)
                    continue
                
                logger.info(f"Email code received: {email_code}")
                
                if not client.verify_email_otp(email, email_code, verified_phone.phone_number):
                    logger.error(f"Failed to verify email {email}")
                    self.phone_queue.put(verified_phone)
                    continue
                
                logger.info(f"✓ Email verified: {email} bound to {verified_phone.phone_number}")
                
                verified_phone.used = True
                verified_phone.bound_email = email
                
                v0_code = client.redeem_code("v0")
                elevenlabs_code = client.redeem_code("elevenlabs")
                
                account = VerifiedAccount(
                    phone_number=verified_phone.phone_number,
                    email=email,
                    session_token=client.session_token,
                    v0_code=v0_code,
                    elevenlabs_code=elevenlabs_code
                )
                
                self.results_queue.put(account)
                logger.info(f"✅ Account completed: {email}")
                
            except Exception as e:
                logger.error(f"Email worker error: {e}")
                time.sleep(10)


class AIStudentPackAutomation:
    
    def __init__(self, config: dict):
        from phone_verifier import PhoneVerifier
        from email_verifier import EmailVerifier
        
        sim_config = config.get('5sim', {})
        phone_token = sim_config.get('token')
        phone_xsrf = sim_config.get('xsrf_token', None)
        
        email_config = config.get('email_verifier', {})
        
        self.phone_verifier = PhoneVerifier(phone_token, phone_xsrf)
        self.email_verifier = EmailVerifier(
            admin_auth=email_config.get('admin_auth'),
            api_base_url=email_config.get('api_base_url'),
            domain=email_config.get('domain')
        )
        
        self.auto_config = config.get('automation', {})
        
        self.phone_queue = queue.Queue(maxsize=10)
        self.results_queue = queue.Queue()
        self.stop_event = threading.Event()
        
        self.phone_worker = None
        self.email_workers = []
        self.results = []
    
    def start(self, num_email_workers: int = 2, target_accounts: int = 5):
        logger.info(f"Starting automation with {num_email_workers} email workers for {target_accounts} accounts")
        
        self.phone_worker = PhoneVerificationWorker(
            self.phone_queue,
            self.phone_verifier,
            self.stop_event,
            target_accounts=target_accounts,
            phone_config=self.auto_config
        )
        self.phone_worker.start()
        
        for _ in range(num_email_workers):
            worker = EmailVerificationWorker(
                self.phone_queue,
                self.email_verifier,
                self.results_queue,
                self.stop_event
            )
            worker.start()
            self.email_workers.append(worker)
        
        logger.info("All workers started")
    
    def stop(self):
        logger.info("Stopping automation...")
        self.stop_event.set()
        
        if self.phone_worker:
            self.phone_worker.join(timeout=5)
        
        for worker in self.email_workers:
            worker.join(timeout=5)
        
        logger.info("All workers stopped")
    
    def get_results(self) -> List[VerifiedAccount]:
        results = []
        while not self.results_queue.empty():
            try:
                account = self.results_queue.get_nowait()
                results.append(account)
                self.results.append(account)
            except queue.Empty:
                break
        return results
    
    def save_results(self, filename: str = "accounts.jsonl"):
        if not self.results:
            logger.info("No new results to save")
            return
            
        with open(filename, 'a') as f:
            for acc in self.results:
                json_line = json.dumps(asdict(acc), ensure_ascii=False)
                f.write(json_line + '\n')
        
        logger.info(f"Appended {len(self.results)} new accounts to {filename}")
        
        self.results.clear()
    
    @staticmethod
    def load_accounts(filename: str = "accounts.jsonl") -> List[VerifiedAccount]:
        accounts = []
        try:
            with open(filename, 'r') as f:
                for line in f:
                    if line.strip():
                        data = json.loads(line)
                        acc = VerifiedAccount(**data)
                        accounts.append(acc)
            logger.info(f"Loaded {len(accounts)} accounts from {filename}")
        except FileNotFoundError:
            logger.info(f"No existing {filename} file found")
        except Exception as e:
            logger.error(f"Error loading accounts: {e}")
        return accounts
    
    def run(self, target_accounts: int = 5, max_time: int = 3600, num_email_workers: int = 2):
        self.start(num_email_workers=num_email_workers, target_accounts=target_accounts)
        start_time = time.time()
        
        try:
            while len(self.results) < target_accounts:
                if time.time() - start_time > max_time:
                    logger.warning("Timeout reached")
                    break
                
                new_results = self.get_results()
                for acc in new_results:
                    logger.info(f"✅ Account {len(self.results)}/{target_accounts}: {acc.email}")
                    logger.info(f"   v0: {acc.v0_code}")
                    logger.info(f"   ElevenLabs: {acc.elevenlabs_code}")
                
                time.sleep(5)
            
        except KeyboardInterrupt:
            logger.info("Interrupted by user")
        finally:
            self.stop()
            self.save_results()
            
            logger.info(f"\n=== Final Results ===")
            logger.info(f"Total accounts created: {len(self.results)}")
            for idx, acc in enumerate(self.results, 1):
                logger.info(f"{idx}. {acc.email} / {acc.phone_number}")
                logger.info(f"   v0: {acc.v0_code}")
                logger.info(f"   ElevenLabs: {acc.elevenlabs_code}")

